#include "defaults.h"
#include "nurbs.h"
#include "utils.h"
#include "io.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>

Primitive* OpenPrimitive(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return 0;
	}
	std::string buf;
	file >> buf;
	file.close();
	if(buf == "curve") {
		Curve* curve = OpenCurve(filename);
		return curve;
	} if(buf == "surface") {
		Surface* surface = OpenSurface(filename);
		return surface;
	} else if(buf == "trianglemesh") {
		STriangleMesh* trianglemesh = OpenTriangleMesh(filename);
		return trianglemesh;
	} else if(buf == "nurbssurfacemesh") {
		NurbsSurfaceMesh* nsm = OpenNurbsSurfaceMesh(filename);
		return nsm;
	} else {
		std::string str = FileExtension(filename);

		if(str == "off") {
			std::cout << "reading .off..." << std::endl;
			std::pair< std::vector<Vec3n>, std::vector< std::vector<size_t> > > obj = ReadOFF<3, nfloat, size_t>(filename);
			STriangleMesh* trianglemesh = new STriangleMesh(obj.first, obj.second);
			return trianglemesh;
		} else {
			std::cerr << "cannot read file " << filename << std::endl;
			return 0;
		}
	}
	return 0;
}

Curve* OpenCurve(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return 0;
	}
	char buf[256];
	file >> buf;
	if(strcmp(buf, "curve") != 0) {
		std::cerr << "\"" << filename << "\" is not a curve." << std::endl;
		return 0;
	}

	int p;
	file >> p;
	int n;
	file >> n;
	std::vector<Vec4n> Pw(n);
		
	for(int i = 0; i < n; i++) {
		Vec4n& P = Pw[i];
		file >> P[0] >> P[1] >> P[2] >> P[3];
	}

	int knots;
	file >> knots;
	std::vector<nfloat> U(knots);
	for(int i = 0; i < knots; i++)
		file >> U[i];
	file.close();
	return new Curve(Pw, p, U);
}

Surface* OpenSurface(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return 0;
	}
	char buf[256];
	file >> buf;
	if(strcmp(buf, "surface") != 0) {
		std::cerr << "\"" << filename << "\" is not a surface." << std::endl;
		return 0;
	}

	int p, q;
	file >> p >> q;
	int n, m;
	file >> n >> m;
	std::vector< std::vector<Vec4n> > Pw(n);
	for(int i = 0; i < n; i++)
		Pw[i].resize(m);
	
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < m; j++) {
			Vec4n& P = Pw[i][j];
			file >> P[0] >> P[1] >> P[2] >> P[3];
		}
	}
	int uknots;
	file >> uknots;
	std::vector<nfloat> U(uknots);
	for(int i = 0; i < uknots; i++)
		file >> U[i];
		
	int vknots;
	file >> vknots;
	std::vector<nfloat> V(vknots);
	for(int i = 0; i < vknots; i++)
		file >> V[i];

	return new Surface(Pw, p, q, U, V);
}

NurbsSurfaceMesh* OpenNurbsSurfaceMesh(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return 0;
	}
	
	std::string str;
	file >> str;
	if(str != "nurbssurfacemesh") {
		std::cerr << "\"" << filename << "\" is not a nurbssurfacemesh." << std::endl;
		file.close();
		return 0;
	}

	std::vector<Vec3n> vposition;
	
	std::vector< boost::multi_array<size_t, 2> > element;
	std::vector< math::Vec2ui> degree;
	std::vector< std::pair<Knotvector, Knotvector> > knotvector;
	while(file) {
		getline(file, str);
		std::vector<std::string> token;
		boost::algorithm::split(token, str, boost::algorithm::is_space());

		if(token[0] == "#") {
			std::cerr << str << std::endl;
		} else if(token[0] == "position") {
			Vec3n p;
			std::stringstream ss(std::stringstream::in | std::stringstream::out);
			ss << token[1] << " " << token[2] << " " << token[3];
			ss >> p[0] >> p[1] >> p[2];
			vposition.push_back(p);
		} else if(token[0] == "element") {
			size_t p, q;
			Knotvector U, V;
			{
				getline(file, str);
				std::stringstream ss(str);
				ss >> p >> q;
			}
			degree.push_back(math::V2ui(p, q));
			{
				getline(file, str);
				std::stringstream ss(str);
				for(size_t i = 0; i < 2 * (p + 1); i++) {
					nfloat t;
					ss >> t;
					U.push_back(t);
				}
			}
			{
				getline(file, str);
				std::stringstream ss(str);
				for(size_t i = 0; i < 2 * (q + 1); i++) {
					nfloat t;
					ss >> t;
					V.push_back(t);
				}
			}
			knotvector.push_back(std::pair<Knotvector, Knotvector>(U, V));

			{
				element.push_back(boost::multi_array<size_t, 2>(boost::extents[p + 1][q + 1]));
				boost::multi_array<size_t, 2>& c = element.back();
				for(size_t j = 0; j <= p; j++) {
					getline(file, str);
					std::stringstream ss(str);
			
					for(size_t k = 0; k <= q; k++) {
						ss >> c[j][k];
					}
				}
			}
		} 
	}
	file.close();
	
	std::vector< std::pair<std::string, Variant> > vertexdata, nurbsdata;
	vertexdata.push_back(std::pair<std::string, Variant>("position", vposition));
	PRINTVAR(knotvector.size());

	return new NurbsSurfaceMesh(vposition.size(), vertexdata, nurbsdata, element, knotvector, degree[0][0], degree[0][1]);
}

STriangleMesh* OpenTriangleMesh(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return 0;
	}
	char buf[256];
	file >> buf;
	if(strcmp(buf, "trianglemesh") != 0 && strcmp(buf, "mtrianglemesh") != 0) {
		std::cerr << "\"" << filename << "\" is not a triangle mesh." << std::endl;
		return 0;
	}

	bool readParam = false;
	if(strcmp(buf, "mtrianglemesh") == 0)
		readParam = true;

	size_t numVertices;
	file >> numVertices;
	std::vector<Vec3n> vertex(numVertices);
	Vec3n min = math::V3n(std::numeric_limits<nfloat>::max(), std::numeric_limits<nfloat>::max(), std::numeric_limits<nfloat>::max());
	Vec3n max = (-1.0) * min;

	if(!readParam) {
		for(size_t i = 0; i < numVertices; i++) {
			Vec3n& P = vertex[i];
			file >> P[0] >> P[1] >> P[2];
			min = Min(min, P);
			max = Max(max, P);
		}
	} else {
		for(size_t i = 0; i < numVertices; i++) {
			Vec3n& P = vertex[i];
			nfloat u;
			nfloat v;
			nfloat w;
			
			file >> P[0] >> P[1] >> P[2] >> u >> v >> w;
			min = Min(min, P);
			max = Max(max, P);
		}
	}

	size_t numTriangles;
	file >> numTriangles;
	std::vector< std::vector<size_t> > index(numTriangles);
	for(size_t i = 0; i < numTriangles; i++) {
		index[i].resize(3);
		file >> index[i][0];
		file >> index[i][1];
		file >> index[i][2];
	}

	file.close();

	return new STriangleMesh(vertex, index);
}

STriangleMesh ReadOBJ(const std::string& filename) {
	std::ifstream file;
	file.open(filename.c_str());
	if(!file) {
		std::cerr << "Could not open file \"" << filename << "\"." << std::endl;
		return STriangleMesh();
	}

	std::vector<Vec3n> position;
	std::vector<nfloat> texu;
	std::vector<nfloat> texv;

	std::vector<size_t> indexv, indext;
	while(file) {
		std::string str;
		getline(file, str);
		std::vector<std::string> token;
		boost::algorithm::split(token, str, boost::algorithm::is_space());
		if(token[0] == "#") {
			std::cerr << str << std::endl;
		} else if(token[0] == "v") {
			Vec3n v;
			std::stringstream ss(std::stringstream::in | std::stringstream::out);
			ss << token[1] << " " << token[2] << " " << token[3];
			ss >> v[0] >> v[1] >> v[2];
			position.push_back(v);
		} else if(token[0] == "vt") {
			Vec2n p;
			std::stringstream ss(std::stringstream::in | std::stringstream::out);
			ss << token[1] << " " << token[2];
			ss >> p[0] >> p[1];
			texu.push_back(p[0]);
			texv.push_back(p[1]);
		} else if(token[0] == "f") {
			for(size_t i = 1; i < token.size(); i++) {
				std::vector<std::string> itoken;
				boost::algorithm::split(itoken, token[i], boost::algorithm::is_any_of("/"));
				std::stringstream ss(std::stringstream::in | std::stringstream::out);
				ss << itoken[0] << " " << itoken[1];
				size_t a, b;
				ss >> a >> b;
				a--;
				b--;
				indexv.push_back(a);
				indext.push_back(b);
			}
		}
	}

	std::vector< std::pair<std::string, Variant> > vertexdata;
	vertexdata.push_back(std::pair<std::string, Variant>("position", position));
	vertexdata.push_back(std::pair<std::string, Variant>("u", texu));
	vertexdata.push_back(std::pair<std::string, Variant>("u", texv));
	std::vector<math::Vec3ui> tindex(indexv.size() / 3);
	for(size_t i = 0; i < tindex.size(); i++) {
		tindex[i] = math::V3ui(indexv[3 * i], indexv[3 * i + 1], indexv[3 * i + 2]);
	}

	std::vector< std::pair<std::string, Variant> > triangledata;
	return STriangleMesh(position.size(), vertexdata, triangledata, tindex);
}

void WriteOFF(STriangleMesh* tm, const std::string& filename) {
	std::ofstream file;
	file.open(filename.c_str());
	file << std::setprecision(8);

	const size_t numVertices = tm->GetNumVertices();
	const size_t numTriangles = tm->GetNumTriangles();

	file << "OFF" << std::endl;
	file << numVertices << " " << numTriangles << " " << 0 << std::endl;

	for(size_t i = 0; i < numVertices; i++) {
 		const Vec3n& P = tm->Vertex(i)->Get<Vec3n>("position");
 		file << P[0] << " " << P[1] << " " << P[2] << std::endl;
	}
	
	for(size_t i = 0; i < numTriangles; i++) {
		STriangle* t = tm->Triangle(i);
		file << 3 << " " << t->vertex[0]->Id() << " " << t->vertex[1]->Id() << " " << t->vertex[2]->Id() << std::endl;
	}

	file.close();
}

